#pragma once
/*这个文件是对brpc的二次封装，是结合服务的注册和发现来封装的*/

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include <brpc/channel.h>
#include "logger.hpp"

namespace ChatSystem
{
    //1. 封装单个服务的信道管理类
    class ServiceChannel
    {
    public:
        using ptr = std::shared_ptr<ChatSystem::ServiceChannel>;
        using ChannelPtr = std::shared_ptr<brpc::Channel>;
        ServiceChannel(const std::string& name)
        :_service_name(name), _index(0)
        {}
        ~ServiceChannel()
        {}
        //服务上线了一个节点，则调用append新增信道
        void append(const std::string& host)
        {
            //先实例化一个对应的信道
            ChannelPtr channel = std::make_shared<brpc::Channel>();
            brpc::ChannelOptions options;
            options.connect_timeout_ms = -1;    //连接等待超时时间，-1表示一直等待；
            options.timeout_ms = -1;    //rpc请求超时时间，-1表示一直有效
            options.protocol = "baidu_std"; //序列化协议，默认使用baidu_std
            options.max_retry = 3;  //请求重试次数
            int ret = channel->Init(host.c_str(), &options);
            if(ret == -1)
            {
                LOG_ERROR("初始化{}-{}失败！", _service_name, host);
                return;
            }
            //加锁
            std::unique_lock<std::mutex> lock(_mutex);
            _hosts.insert({host, channel});
            _channels.push_back(channel);
            
        }
        //服务下线了一个节点，则调用remove释放对应的信道
        void remove(const std::string& host)
        {
            //先加锁
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _hosts.find(host);
            if(it == _hosts.end())
            {
                LOG_WARN("{}-{}节点删除信道时，没有找到信道信息！", _service_name, host);
                return;
            }
            for(auto vit = _channels.begin(); vit != _channels.end(); ++vit)
            {
                if(*vit == it->second)
                {
                    _channels.erase(vit);
                    break;
                }
            }
            _hosts.erase(host);

        }
        //通过RR轮转策略进行负载均衡，获取ChannelPtr用于发起对应服务的Rpc调用
        ChannelPtr choose()
        {
            //加锁
            std::unique_lock<std::mutex> lock(_mutex);
            if(_channels.size() == 0)
            {
                LOG_ERROR("当前没有能够提供 {} 服务的节点！", _service_name);
                return ChannelPtr();    //返回空
            }
            int32_t idx = _index++ % _channels.size();
            return _channels[idx];
        }
    private:
        std::mutex _mutex;  //多个线程可能会同时访问，为了保证线程安全需要加锁
        int32_t _index; //当前轮转下标计数器---对当前服务进行负载均衡；
        std::string _service_name;  //服务名称
        std::vector<ChannelPtr> _channels;  //当前服务对应的信道集合---使用vector对信道进行组织是为了方便RR轮转式的负载均衡
        std::unordered_map<std::string, ChannelPtr> _hosts; //主机地址于信道的映射关系---为了便于在服务下线的时候删除对应的信道，服务的下线情况肯定少；
    };

    //2. 总体的服务信道管理类
    class ServiceManager
    {
    public:
        using ptr = std::shared_ptr<ChatSystem::ServiceManager>;
        ServiceManager()
        {}
        ~ServiceManager()
        {}
        //先声明，我关注哪些服务的上线和下线，不关心的就不需要管理了
        void declared(const std::string& service_name)
        {
            //先加锁
            std::unique_lock<std::mutex> lock(_mutex);
            _follow_services.insert(service_name);
        }

        //根据服务名获取对应的服务节点信道
        ChatSystem::ServiceChannel::ChannelPtr choose(const std::string& service_name)
        {
            //先加锁
            std::unique_lock<std::mutex> lock(_mutex);
            auto sit = _services.find(service_name);
            if(sit == _services.end())
            {
                LOG_ERROR("当前没有能够提供 {} 服务的节点！", service_name);
                return ChatSystem::ServiceChannel::ChannelPtr();
            }
            return sit->second->choose();
        }

        /*在实例化etcd服务发现类对象的时候我们需要设置服务上线后和服务下线后的回调函数,
        在服务上线后的回调函数设置为onServiceOnline， 在服务下线后的回调函数设置为onServiceOffline*/
        //服务节点上线时调用的回调接口，如果该服务需要关心上线和下线就将服务节点管理起来，服务存在了就给该服务添加节点
        void onServiceOnline(const std::string& service_instance, const std::string& host)
        {
            //获取服务的名称，而不是用服务节点的名称来删除指定服务的信道
            std::string service_name = getServiceName(service_instance);
            //先获取服务管理对象，没有则创建一个服务管理，服务管理对象存在则添加该服务的新节点
            ChatSystem::ServiceChannel::ptr service;
            {
                std::unique_lock<std::mutex> lock(_mutex);

                auto fit = _follow_services.find(service_name);
                if(fit == _follow_services.end())
                {
                    LOG_DEBUG("{}-{} 服务节点上线了，但是当前服务并不需要关注！");
                    return;
                }

                auto sit = _services.find(service_name);
                if(sit == _services.end())
                {
                    service = std::make_shared<ChatSystem::ServiceChannel>(service_name);
                    _services.insert({service_name, service});
                }
                else
                {
                    service = sit->second;
                }
            }//这里添加一个作用域是为了防止_mutex的加锁和append接口中加锁的操作重叠了
            if(!service)
            {
                LOG_ERROR("新增 {} 服务管理节点失败！", service_name);
                return;
            }
            //给服务添加节点
            service->append(host);
            LOG_DEBUG("{}-{} 服务上线新节点，进行添加管理！", service_name, host);
        }
        //服务节点下线时的回调接口，从服务信道管理中，删除指定该服务的信道
        void onServiceOffline(const std::string& service_instance, const std::string& host)
        {
            //获取服务的名称，而不是用服务节点的名称来删除指定服务的信道
            std::string service_name = getServiceName(service_instance);
            //先获取服务管理对象，再删除服务节点
            ChatSystem::ServiceChannel::ptr service;
            {
                std::unique_lock<std::mutex> lock(_mutex);

                auto fit = _follow_services.find(service_name);
                if(fit == _follow_services.end())
                {
                    LOG_DEBUG("{}-{} 服务节点下线了，但是当前服务并没有被关注！", service_name, host);
                    return;
                }

                auto sit = _services.find(service_name);
                if(sit == _services.end())
                {
                    LOG_ERROR("删除 {} 服务节点的时，没有找到服务管理对象！", service_name);
                    return;
                }
                else
                {
                    service = sit->second;
                }
            }//这里添加一个作用域是为了防止_mutex的加锁和remove接口中加锁的操作重叠了
            if(!service)
            {
                LOG_ERROR("删除 {} 服务管理节点失败！", service_name);
                return;
            }
            //删除服务节点
            service->remove(host);
            LOG_DEBUG("{}-{}服务下线节点，进行删除管理！", service_name, host);
        }
    private:
        std::string getServiceName(const std::string& service_instance)
        {
            auto pos = service_instance.find_last_of('/');
            if(pos == std::string::npos)
            {
                return service_instance;
            }
            return service_instance.substr(0, pos);
        }
    private:
        std::mutex _mutex;
        std::unordered_set<std::string> _follow_services;   //关注的服务
        std::unordered_map<std::string, ChatSystem::ServiceChannel::ptr> _services; //服务和对应服务信道类的映射
    };
}
